package org.swift.mashup.engine.model;

import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.xml.transform.stream.StreamSource;

import net.sf.saxon.Configuration;
import net.sf.saxon.dom.NodeOverNodeInfo;
import net.sf.saxon.om.DocumentInfo;
import net.sf.saxon.om.NodeInfo;
import net.sf.saxon.query.DynamicQueryContext;
import net.sf.saxon.query.StaticQueryContext;
import net.sf.saxon.query.XQueryExpression;
import net.sf.saxon.trans.XPathException;

import org.swift.commons.xml.XmlUtils;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;


public class XQueryActivity implements Activity {
	private final static ActivityContext NULL_CONTEXT = new ByteArrayActivityContext("<null/>");

	private XQueryExpression xquery;
	
	
	public XQueryActivity(XQueryExpression xquery) {
		this.xquery = xquery;
	}
	
	//TODO hand the namespaces
	public XQueryActivity(String grabName, NodeList returnNodes,
			String xpathExpression, Collection<? extends Parameter> params,
			Map<String, String> namespaces) {					
		try {
			this.xquery = createXQuery(grabName, returnNodes, xpathExpression, params);
		} catch (XPathException e) {
			throw new RuntimeException(e);
		}
	}		
		
	@SuppressWarnings("unchecked")
	public ActivityResultComponent doActivity(ActivityContext context,
			Map<String, ? extends Object> parameters) throws ActivityException {
		if (context.getSize() == 0){
			context = NULL_CONTEXT;
		}
		try {
			if (xquery == null) {
				return new ActivityResultItem(new String(context.getBytes()));			
			}
			
			DocumentInfo info = (DocumentInfo) xquery.getStaticContext().getUserQueryContext()
					.buildDocument(new StreamSource(
							new StringReader(
									new String(context.getBytes()))));

			DynamicQueryContext dynamicContext = new DynamicQueryContext(
					this.xquery.getStaticContext().getUserQueryContext().getConfiguration());
			
			dynamicContext.setContextItem(info);
			setParameters(dynamicContext,parameters);		
			this.xquery.setAllowDocumentProjection(true);
			
			return new ActivityResultItem(handleResults(this.xquery.evaluate(dynamicContext)));
		} catch (XPathException e) {
			throw new ActivityException(e);
		} catch (IOException e) {
			throw new ActivityException(e);
		}
		
	}

	private void setParameters(DynamicQueryContext dynamicContext,
			Map<String, ? extends Object> parameters) {
		for(Entry<String, ? extends Object> parameter: parameters.entrySet()) {
			dynamicContext.setParameter(parameter.getKey(), parameter.getValue());
		}
	}

	
	private Collection<Object> handleResults(List<? extends Object> evaluate) {
		List<Object> list = new ArrayList<Object>();		
		for(Object obj: evaluate) {
			if (obj instanceof NodeInfo) {
				NodeInfo tree = (NodeInfo)obj;
				list.add(NodeOverNodeInfo.wrap(tree));		
			} else {
				list.add(obj);
			}
		}
		
		return list;
	}

	private XQueryExpression createXQuery(String grabName, NodeList returnNodes,
			String xpathExpression, Collection<? extends Parameter> params)
			throws XPathException {		
		
		String xpath = xpathExpression == null ? "." : xpathExpression;
		final StringBuffer expression = new StringBuffer();
		for(Parameter param : params) {
			if (!grabName.equals(param.getName())){
				expression.append(createParam(param));
			}
		}		
		Configuration config = new Configuration();
		StaticQueryContext staticContext = new StaticQueryContext(config);
		
		expression.append("for $" + grabName + " in " + xpath + " return ");
		if (returnNodes != null) {
			
			XmlUtils.eachNode(returnNodes,
				new XmlUtils.Closure<Node>() {
					public void execute(Node item) {
						if (Node.TEXT_NODE == item.getNodeType()) {
							expression.append(item.getNodeValue());
						} else {
							expression.append(XmlUtils.toString(item));
						}
					}		
			});
		} else {
			expression.append("$" + grabName + "");
		}
		expression.append("");		
		return staticContext.compileQuery(expression.toString());		
	}

	private Object createParam(Parameter parameter) {
		return "declare variable $" + parameter.getName() + " external;\n";
	}
	
	
	

}
